我們現在已經有一個可以執行的指令,接下來就是要將 Web Server 啟動。在 Ruby 裡面我們可以透過兩種方式將 Rack 打開。
config.ru
定義後,用 rackup
開啟Rack::Handler
自動挑選推薦的伺服器(Ex. Puma)開啟在 Ruby on Rails 應該是使用前者,不過 Rails 也做了一些特殊處理讓我們也可以用 rails server
啟動,這邊我們要採用的是第二個方法。
因為 Ruby 已經定義了 Rack 這個通用的介面,因此不同的 Web Server 要能夠被 Rack 使用就會將自己向 Rack 註冊成為執行的主體,也因此 Rack 身上會帶有多個 Web Server 可以使用在預設的情況下會自動選擇一個。
因為 Puma、Unicorn 這類伺服器都是後來註冊的,並且會特地插入到備選列表的前方,因此我們可以直接使用 Rack::Handler.default
來選擇到 Puma。
打開 config/application.rb
稍微修改我們的伺服器讓他可以接收普通的 HTTP 請求。
class SimpleRPG
include Singleton
class << self
def run(server, options = {})
# 呼叫實例上的方法
instance.start(server, options)
end
end
def start(server, options = {})
# TODO: 要透過 Server 選擇負責處理的物件
# 透過 Rack::Handler 預設值啟動伺服器
Rack::Handler.default.run(self, options)
end
# TODO: 實作專門的 HTTP 處理程式
def call(_env)
# 簡單回傳 Hello World
[200, { 'Content-Type' => 'text/plain' }, ['Hello World']]
end
end
現在,我們可以執行 bin/serve map
來把伺服器開啟,預設的 Port 是 9292 因此我們可以用 curl
指令驗證一下。
curl localhost:9292
# => Hello World
在上一篇中我們將
options[:Port] = port&.to_i
是因為 Rack 在設定 Port 是用:Port
當作 Key 而不是:port
有點跟平常習慣的 Ruby 使用方式有點差異。
在 Ruby 裡面並沒有原生的 WebSocket 套件,如果要靠自己實作處理這些行為的話是相當費時的,不過 Faye::WebSocket 已經幫我們封裝好了 websocket-driver
這個 Gem 我們只需要直接使用即可。
# app/servers/websocket_server.rb
# frozen_string_literal: true
module WebSocketServer
UNSUPPORT_RESPONSE = [501, { 'Content-Type' => 'text/plain' }, ['Unsupport']].freeze
def self.call(env)
return UNSUPPORT_RESPONSE unless Faye::WebSocket.websocket?(env)
ws = Faye::WebSocket.new(env)
ws.rack_response
end
end
我們先簡單實作一個可以接受 .call
行為的物件,當 Rack 啟動收到請求就會呼叫這個 .call
行為進行處理。
當我們要處理 WebSocket 之前,需要先了解是否為 WebSocket 連線,如果不是的話大多會再處理後結束連線(現代的伺服器會保持一段時間來重複使用),而 WebSocket 則要明確地告訴伺服器連線要轉換成持久的連線。
因此我們要先透過 Faye::WebSocket.websocket?(env)
來幫我們檢查傳入的資訊是否是 WebSocket 請求,如果不是的話我們就回應一個 Unsupport
(不支援)的訊息。
接下來要調整一下我們的 SimpleRPG 物件讓他可以吃到 WebSocketServer:
# config/application.rb
# frozen_string_literal: true
require 'rubygems'
require 'bundler'
require 'singleton'
require 'app/servers/websocket_server'
Bundler.require
class SimpleRPG
include Singleton
class << self
def run(server, options = {})
instance.start(server, options)
end
end
def start(server, options = {})
# TODO: 要透過 Server 選擇負責處理的物件
Rack::Handler.default.run(WebSocketServer, options)
end
end
我們現在可以打開 Chrome 或者其他瀏覽器的主控台(Console)直接執行一段 JavaScript 連上我們剛寫好的伺服器(請記得關閉伺服器後重開)
var ws = new WebSocket('ws://localhost:9292')
ws.onopen = (ev) => console.log(ev)
如果有看到 JavaScript 事件產生,那麼就表示我們的伺服器已經成功支援 WebSocket 了!
我的個人部落格是弦而時習之平常會把自己發現的一些新技巧紀錄在上面,也歡迎大家來逛逛。